﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;
using Device = SharpDX.Direct3D11.Device;
using Ignitron.EEngine.Model;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using SharpDX.Direct3D;

namespace Ignitron.EEngine.Adapters.DX.Graphics
{
    /// <summary>
    /// SharpDX subclass of a mesh
    /// </summary>
    internal class SharpDXMesh: EMesh
    {
        /// <summary>
        /// Model format
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        public struct ModelFormat
        {
            public float x, y, z;
            public float tu, tv;
            public float nx, ny, nz;
        }

        /// <summary>
        /// DXD11 device and the window handle
        /// </summary>
        private Device mDevice;
        private IntPtr mWindowHandle;

        /// <summary>
        /// Shaders' references
        /// </summary>
        private TextureShader mTextureShader;
        private LightingShader mLightingShader;

        /// <summary>
        /// The camera reference
        /// </summary>
        private Camera mCamera;

        /// <summary>
        /// The class holding the global lighting settings
        /// </summary>
        private LightingManager mLightingManager;

        /// <summary>
        /// Vertices buffer
        /// </summary>
        private Vector3[] mDXVertices;
        private Vector3[] mDXNormals;

        /// <summary>
        /// DX buffers
        /// </summary>
        private Buffer mVertexBuffer;
        private Buffer mIndexBuffer;
        public int IndexCount 
        { get; private set; }

        /// <summary>
        /// Primitives renderer reference
        /// </summary>
        private PrimitivesRenderer mPrimitivesRenderer;

        /// <summary>
        /// Constructs the mesh
        /// </summary>
        public SharpDXMesh(Device device, IntPtr windowHandle, Camera camera, PrimitivesRenderer renderer, LightingManager lightingManager, 
                            TextureShader textureShader, LightingShader lightingShader)
        {
            mDevice = device;
            mWindowHandle = windowHandle;
            mCamera = camera;
            mPrimitivesRenderer = renderer;
            mLightingManager = lightingManager;
            mTextureShader = textureShader;
            mLightingShader = lightingShader;
        }

        /// <summary>
        /// Updates the mesh
        /// </summary>
        protected override void Update()
        {
            // first free the buffers
            FreeBuffers();

            // then create new ones
            bool lightingEnabled = mLightingManager.LightingEnabled;

            if (!lightingEnabled)
            {
                // Create the vertex array.
                var vertices = new TextureShader.Vertex[mVertices.Length];
                mDXVertices = new Vector3[mVertices.Length];

                // Create the index array.
                //var indices = new int[mIndices.Length];

                for (var i = 0; i < mVertices.Length; i++)
                {
                    vertices[i] = new TextureShader.Vertex()
                    {
                        position = new Vector3(mVertices[i].X, mVertices[i].Y, mVertices[i].Z),
                        texture = new Vector2(mTexCoords[i].X, mTexCoords[i].Y),
                        //normal = mNormals.Length > i ? new Vector3(mNormals[i].X, mNormals[i].Y, mNormals[i].Z) : Vector3.Zero
                    };


                    mDXVertices[i] = new Vector3(mVertices[i].X, mVertices[i].Y, mVertices[i].Z);
                }

                // Create the vertex buffer.
                mVertexBuffer = Buffer.Create(mDevice, BindFlags.VertexBuffer, vertices);
            }
            else
            {
                // Create the vertex array.
                var vertices = new LightingShader.Vertex[mVertices.Length];
                mDXVertices = new Vector3[mVertices.Length];

                for (var i = 0; i < mVertices.Length; i++)
                {
                    vertices[i] = new LightingShader.Vertex()
                    {
                        position = new Vector4(mVertices[i].X, mVertices[i].Y, mVertices[i].Z, 1f),
                        texture = new Vector2(mTexCoords[i].X, mTexCoords[i].Y),
                    };

                    // We need this hack because of wrong models 
                    if (i < mNormals.Length)
                    {
                        vertices[i].normal = new Vector3(mNormals[i].X, mNormals[i].Y, mNormals[i].Z);
                    }

                    mDXVertices[i] = new Vector3(mVertices[i].X, mVertices[i].Y, mVertices[i].Z);
                }

                // Create the vertex buffer.
                mVertexBuffer = Buffer.Create(mDevice, BindFlags.VertexBuffer, vertices);
            }

            mDXNormals = new Vector3[mNormals.Length];
            for (var i = 0; i < mNormals.Length; i++)
            {
                mDXNormals[i] = new Vector3()
                {
                    X = mNormals[i].X,
                    Y = mNormals[i].Y,
                    Z = -mNormals[i].Z,
                };
            }


            // Change the triangle order of rendering for RHS
            uint[] indices = new uint[mIndices.Length];

            for (int i = 0; i < mIndices.Length; i += 3)
            {
                indices[i] = mIndices[i + 1];
                indices[i + 1] = mIndices[i];
                indices[i + 2] = mIndices[i + 2];
            }          

            // Create the index buffer.
            mIndexBuffer = Buffer.Create(mDevice, BindFlags.IndexBuffer, indices);
        }

        
        /// <summary>
        /// Rendering method
        /// </summary>
        internal void Render(Matrix world, ShaderResourceView texture)
        {
            if (mLightingManager.LightingEnabled)
            {
                mLightingShader.SetTransformMatrices(world, mCamera.ViewMatrix, mCamera.ProjectionMatrix);
            }
            else
            {
                mTextureShader.SetTransformMatrices(world, mCamera.ViewMatrix, mCamera.ProjectionMatrix);
            }

            DeviceContext deviceContext = mDevice.ImmediateContext;
            // Set the vertex buffer to active in the input assembler so it can be rendered.
            if (mLightingManager.LightingEnabled)
            {
                deviceContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(mVertexBuffer, Utilities.SizeOf<LightingShader.Vertex>(), 0));
            }
            else
            {
                deviceContext.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(mVertexBuffer, Utilities.SizeOf<TextureShader.Vertex>(), 0));
            }
            // Set the index buffer to active in the input assembler so it can be rendered.
            deviceContext.InputAssembler.SetIndexBuffer(mIndexBuffer, Format.R32_UInt, 0);
            // Set the type of the primitive that should be rendered from this vertex buffer, in this case triangles.
            deviceContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;


            // choose texture or lighting shader
            if (mLightingManager.LightingEnabled)
            {
                mLightingShader.Render(mIndices.Length, texture, mCamera.GetPosition(), mCamera.AlphaOpacity, mLightingManager);
            }
            else
            {
                mTextureShader.Render(mIndices.Length, texture);
            }
        }

        /// <summary>
        /// Method for rendering normal vectors
        /// </summary>
        internal void RenderNormals(Matrix world, Vector4 color, float magnitude)
        {
            mPrimitivesRenderer.SetTransformMatrices(world, mCamera.ViewMatrix, mCamera.ProjectionMatrix);
            mPrimitivesRenderer.BeginLines();

            for (int i = 0; i < mDXNormals.Length; i++)
            {
                Vector3 v0 = mDXVertices[i];
                Vector3 n = mDXNormals[i];
                n.Normalize();
                n *= magnitude;
                Vector3 v1 = v0 + n;

                mPrimitivesRenderer.RenderLine(v0, v1, color);
            }

            mPrimitivesRenderer.EndLines();
        }

        /// <summary>
        /// Frees the vertex and index buffer
        /// </summary>
        private void FreeBuffers()
        {
            if (mVertexBuffer != null)
            {
                mVertexBuffer.Dispose();
                mVertexBuffer = null;
            }
            if (mIndexBuffer != null)
            {
                mIndexBuffer.Dispose();
                mIndexBuffer = null;
            }
        }

        /// <summary>
        /// Frees the meshe's resources
        /// </summary>
        public void Shutdown()
        {
            FreeBuffers();
        }
    }
}
